﻿using Microsoft.ExtendedReflection.Monitoring;
using Microsoft.VisualStudio.TestTools.Common;
using Microsoft.VisualStudio.TestTools.Execution;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace MolesVS2012.Host.Host
{
    public class SafeRunContext : IRunContext, IBaseRunContext
    {
        private static readonly bool needsRemoteDataCollector = typeof(IRunContext).Assembly.GetName().Version.Major > 9;
        protected readonly IRunContext runContext;
        private string _agentName;
        private IContextAccessor _runContextVariables;
        private IResultSink _resultSink;
        private IExecutionRunConfiguration _runConfig;

        public string AgentName
        {
            get
            {
                if (this._agentName == null)
                {
                    using (_ProtectingThreadContext.Acquire())
                        this._agentName = this.runContext.AgentName;
                }
                return this._agentName;
            }
        }

        public IContextAccessor RunContextVariables
        {
            get
            {
                if (this._runContextVariables == null)
                {
                    using (_ProtectingThreadContext.Acquire())
                        this._runContextVariables = (IContextAccessor)new SafeContextAccessor(this.runContext.RunContextVariables);
                }
                return this._runContextVariables;
            }
        }

        public IResultSink ResultSink
        {
            get
            {
                if (this._resultSink == null)
                {
                    using (_ProtectingThreadContext.Acquire())
                        this._resultSink = (IResultSink)new SafeResultSink(this.runContext.ResultSink);
                }
                return this._resultSink;
            }
        }

        public IExecutionRunConfiguration RunConfig
        {
            get
            {
                if (this._runConfig == null)
                {
                    using (_ProtectingThreadContext.Acquire())
                        this._runConfig = (IExecutionRunConfiguration)new SafeExecutionRunConfiguration(this.runContext.RunConfig);
                }
                return this._runConfig;
            }
        }

        public ITestRunner TestRunner
        {
            get
            {
                using (_ProtectingThreadContext.Acquire())
                    return this.runContext.TestRunner;
            }
        }

        static SafeRunContext()
        {
        }

        public SafeRunContext(IRunContext runContext)
        {
            this.runContext = runContext;
        }

        public void PauseTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
                this.runContext.PauseTestRun();
        }

        public void StopTestRun()
        {
            using (_ProtectingThreadContext.Acquire())
                this.runContext.StopTestRun();
        }

        public static IRunContext CreateSafeRunContext(IRunContext runContext)
        {
            if (!SafeRunContext.needsRemoteDataCollector)
                return (IRunContext)new SafeRunContext(runContext);
            else
                return SafeRunContext.Context2010.CreateContext(runContext);
        }

        public static class Context2010
        {
            private static readonly Type safeRunDataCollectorType;

            static Context2010()
            {
                Type type = typeof(ITestElement).Assembly.GetType("Microsoft.VisualStudio.TestTools.Utility.IRemoteDataProvider", true);
                Type iRunContextType = typeof(IRunContext);
                Type safeRunContextType = typeof(SafeRunContext);
                FieldInfo field = safeRunContextType.GetField("runContext", BindingFlags.Instance | BindingFlags.NonPublic);
                TypeBuilder typeBuilder = SafeRunContext.Context2010.CreateTypeBuilder(type, safeRunContextType);
                SafeRunContext.Context2010.EmitAttributes(typeBuilder);
                SafeRunContext.Context2010.EmitConstructor(iRunContextType, safeRunContextType, typeBuilder);
                SafeRunContext.Context2010.EmitGetColumns(type, field, typeBuilder);
                SafeRunContext.Context2010.EmitReadTable(type, field, typeBuilder);
                SafeRunContext.Context2010.safeRunDataCollectorType = typeBuilder.CreateType();
            }

            public static IRunContext CreateContext(IRunContext runContext)
            {
                return (IRunContext)Activator.CreateInstance(SafeRunContext.Context2010.safeRunDataCollectorType, new object[1]
        {
          (object) runContext
        });
            }

            private static TypeBuilder CreateTypeBuilder(Type iRemoteDataCollectorType, Type safeRunContextType)
            {
                return AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("MolesVS2012.Host.RunContexts"), AssemblyBuilderAccess.Run).DefineDynamicModule("MolesVS2012.Host.RunContexts.dll", false).DefineType("MolesVS2012.Host.Host.SafeRunContentWithDataCollectors", TypeAttributes.Public | TypeAttributes.Sealed, safeRunContextType, new Type[1]
        {
          iRemoteDataCollectorType
        });
            }

            private static void EmitGetColumns(Type iRemoteDataCollectorType, FieldInfo safeRunContextRunContextField, TypeBuilder typeBuilder)
            {
                MethodInfo method = iRemoteDataCollectorType.GetMethod("GetColumns");
                MethodBuilder methodBuilder = typeBuilder.DefineMethod("GetColumns", MethodAttributes.Public | MethodAttributes.Virtual, typeof(List<string>), new Type[3]
        {
          typeof (string),
          typeof (string),
          typeof (string)
        });
                ILGenerator ilGenerator = methodBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, safeRunContextRunContextField);
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ilGenerator.Emit(OpCodes.Ldarg_2);
                ilGenerator.Emit(OpCodes.Ldarg_3);
                ilGenerator.EmitCall(OpCodes.Callvirt, method, Type.EmptyTypes);
                ilGenerator.Emit(OpCodes.Ret);
                typeBuilder.DefineMethodOverride((MethodInfo)methodBuilder, method);
            }

            private static void EmitReadTable(Type iRemoteDataCollectorType, FieldInfo safeRunContextRunContextField, TypeBuilder typeBuilder)
            {
                MethodInfo method = iRemoteDataCollectorType.GetMethod("ReadTable");
                MethodBuilder methodBuilder = typeBuilder.DefineMethod("ReadTable", MethodAttributes.Public | MethodAttributes.Virtual, method.ReturnType, new Type[3]
        {
          typeof (string),
          typeof (string),
          typeof (string)
        });
                ILGenerator ilGenerator = methodBuilder.GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, safeRunContextRunContextField);
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ilGenerator.Emit(OpCodes.Ldarg_2);
                ilGenerator.Emit(OpCodes.Ldarg_3);
                ilGenerator.EmitCall(OpCodes.Callvirt, method, Type.EmptyTypes);
                ilGenerator.Emit(OpCodes.Ret);
                typeBuilder.DefineMethodOverride((MethodInfo)methodBuilder, method);
            }

            private static void EmitConstructor(Type iRunContextType, Type safeRunContextType, TypeBuilder typeBuilder)
            {
                ILGenerator ilGenerator = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.HasThis, new Type[1]
        {
          iRunContextType
        }).GetILGenerator();
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldarg_1);
                ilGenerator.Emit(OpCodes.Call, safeRunContextType.GetConstructor(new Type[1]
        {
          iRunContextType
        }));
                ilGenerator.Emit(OpCodes.Ret);
            }

            private static void EmitAttributes(TypeBuilder typeBuilder)
            {
                typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(SerializableAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
                typeBuilder.SetCustomAttribute(new CustomAttributeBuilder(typeof(__ProtectAttribute).GetConstructor(Type.EmptyTypes), new object[0]));
            }
        }
    }
}
